<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>001-管网绘图</title>
  <style>
    body {
      background-color: #000;
      text-align: center;
    }

    canvas {
      background-color: #fff;
    }

    #map {
      width: 800px;
      height: 800px;
      margin: 0 auto;
      overflow: auto;
    }
  </style>
</head>

<body>
  <div id="map"></div>

  <script>
    const data = [
      { // 监测点
        name: '永安桥南液位计',              // 监测点名称
        liquidHeight: 1.513,                // 液位(m)
        wellDistance: 86,                   // 井间距离(m)
        pipeDiameter: 1100,                 // 管径或管高(mm)
        groundElevation: 4.100,             // 地面标高(m)
        pipeUpStreamElevation: 1.213,       // 上游管内底(m)
        pipeDownStreamElevation: -0.217     // 下游管内底(m)
      },
      {
        name: '永安桥南液位计',
        liquidHeight: 1.170,
        wellDistance: 44,
        pipeDiameter: 600,
        groundElevation: 5.472,
        pipeUpStreamElevation: 0.272,
        pipeDownStreamElevation: 0.217
      },
      {
        name: '永安桥倒虹',
        liquidHeight: 0.990,
        wellDistance: 30,
        pipeDiameter: 1200,
        groundElevation: 5.417,
        pipeUpStreamElevation: 0.067,
        pipeDownStreamElevation: -0.030
      },
      {
        name: '永安桥倒虹',
        liquidHeight: 0.870,
        wellDistance: 18,
        pipeDiameter: 1200,
        groundElevation: 4.649,
        pipeUpStreamElevation: -0.120,
        pipeDownStreamElevation: 0.140
      },
      {
        name: '晋陵路关河路',
        liquidHeight: 0.990,
        wellDistance: 35,
        pipeDiameter: 1000,
        groundElevation: 4.541,
        pipeUpStreamElevation: 0.241,
        pipeDownStreamElevation: -0.220
      },
      {
        name: '晋陵路关河路',
        liquidHeight: 0.580,
        wellDistance: 81,
        pipeDiameter: 1000,
        groundElevation: 4.437,
        pipeUpStreamElevation: -0.063,
        pipeDownStreamElevation: -0.018
      },
      {
        name: '晋陵路关河路液位计',
        liquidHeight: 0.520,
        wellDistance: 53,
        pipeDiameter: 1100,
        groundElevation: 5.382,
        pipeUpStreamElevation: -0.018,
        pipeDownStreamElevation: 0.212
      },
      {
        name: '晋陵路关河路液位计',
        liquidHeight: 0.550,
        wellDistance: 0,
        pipeDiameter: 0,
        groundElevation: 5.912,
        pipeUpStreamElevation: 0,
        pipeDownStreamElevation: 0
      }
    ]
    let canvas = null
    let context = null
    let width = 200
    let height = 200
    let realWidthPre = 0
    window.onload = function () {
      const canvasDivId = 'map'
      const canvasWidth = 800
      const canvasHeight = 400
      const canvasParams = [canvasDivId, canvasWidth, canvasHeight]
      initCanvas(canvasParams)

      // 内边距系数
      const paddingScale = 10

      // 上下左右内边距
      const topPadding = canvasHeight / paddingScale / 4
      const bottomPadding = canvasHeight / paddingScale / 4
      const leftPadding = canvasWidth / paddingScale / 4
      const rightPadding = canvasWidth / paddingScale / 2

      // 图例宽度
      const legendWidth = canvasWidth / paddingScale * 2

      // 实际宽度。减去左右内边距与图例
      const factWidth = canvasWidth - leftPadding - rightPadding - legendWidth
      // 实际高度。减去上下内边距
      const factHeight = canvasHeight - topPadding - bottomPadding
      // x,y的起始坐标
      const startX = leftPadding
      const startY = topPadding

      // 最大最小标高，用于计算整个图形的高度比例
      let maxElevation = 0
      let minElevation = 0
      // 管径距离总和，用于计算整个图形的宽度比例
      let sumDistance = 0

      data.forEach((place, index) => {
        const pipeElevation = Math.min(place.pipeUpStreamElevation, place.pipeDownStreamElevation)
        if (index === 0) {
          maxElevation = place.groundElevation
        }
        if (place.groundElevation > maxElevation) {
          maxElevation = place.groundElevation
        }
        // 最小标高。如果最小标高大于0，则采用0（黄海基准面）为最小标高
        if (pipeElevation < minElevation) {
          minElevation = pipeElevation
        }
        if (index < data.length - 1) {
          sumDistance += place.wellDistance
        }
      })
      // 图形从上到下 分为 3 个部分，分别为 序号:图形主体:图形说明 = 3:10:7
      // 高度的比例系数如下：
      const heightScale = factHeight / ((maxElevation - minElevation) * 2)
      const numHeight = factHeight / 10 * 1
      const graphHeight = factHeight / 10 * 5
      const descripHeight = factHeight / 10 * 4
      // 最小宽度
      // const minWidth = 80
      // 宽度的比例系数如下：
      // const widthScale = (factWidth - (data.length - 1) * minWidth ) / sumDistance
      const widthScale = factWidth / sumDistance

      // 图例高度
      const legendHeight = descripHeight / 3 * 0.8

      // 最大地面标高 与 0米海拔 的高度差
      const zeroHeight = maxElevation * heightScale

      // 图形主体XY坐标起点
      const graphX = leftPadding + legendWidth
      const graphY = startY + numHeight

      // 黄海基准面Y坐标
      const elevationY = graphY + zeroHeight
      // 管径Y坐标
      const pipeDiameterY = graphY + graphHeight + (descripHeight / 3 + descripHeight / 3 / 2 / 2)
      // 管段Y坐标
      const pipeSelectionY = graphY + graphHeight + (descripHeight / 3 * 2 + descripHeight / 3 / 2)

      const lines = []
      const texts = []
      let x = 0
      let beforePlace = null
      let beforePlaceCoords = null
      data.forEach((place, index) => {
        if (isNaN(place.groundElevation)) return

        if (index === 0) {
          x += graphX
        } else {
          x += beforePlace.wellDistance * widthScale
        }
        // 1. 地面标高坐标
        const topY = graphY + (zeroHeight - place.groundElevation * heightScale)
        const ground = [x, topY]

        // 2. 液位坐标
        const liquid = []
        if (!isNaN(place.liquidHeight)) {
          const liquidY = graphY + (zeroHeight - place.liquidHeight * heightScale)
          liquid.push(x)
          liquid.push(liquidY)
        }

        // 3. 管道坐标-上与下
        const pipeUp = []
        const pipeDown = []
        if (!isNaN(place.pipeUpStreamElevation)) {
          const pipeUpY = graphY + (zeroHeight - (place.pipeUpStreamElevation + place.pipeDiameter / 1000) * heightScale)
          const pipeDownY = graphY + (zeroHeight - place.pipeUpStreamElevation * heightScale)
          pipeUp.push(x)
          pipeUp.push(pipeUpY)
          pipeDown.push(x)
          pipeDown.push(pipeDownY)
        }
        // 同一段管道下游部分
        const pipeUpNext = []
        const pipeDownNext = []
        if (index !== 0 && !isNaN(beforePlace.pipeDownStreamElevation)) {
          const pipeUpNextY = graphY + (zeroHeight - (beforePlace.pipeDownStreamElevation + beforePlace.pipeDiameter / 1000) * heightScale)
          const pipeDownNextY = graphY + (zeroHeight - beforePlace.pipeDownStreamElevation * heightScale)
          pipeUpNext.push(x)
          pipeUpNext.push(pipeUpNextY)
          pipeDownNext.push(x)
          pipeDownNext.push(pipeDownNextY)
        }

        // 4. 黄海基准面
        const elevation = [x, elevationY]

        // 4. 管径
        const diameter = [x, pipeDiameterY]

        // 6. 管段
        const selection = [x, pipeSelectionY]

        // 7. 竖向终点坐标
        const bottomY = graphY + graphHeight + descripHeight
        const bottom = [x, bottomY]

        const coords = [
          ground,
          liquid,
          pipeUp,
          pipeDown,
          pipeUpNext,
          pipeDownNext,
          elevation,
          diameter,
          selection,
          bottom
        ]

        // 竖向-线的一组坐标
        lines.push({
          startX: ground[0],
          startY: ground[1],
          endX: bottom[0],
          endY: bottom[1]
        })
        // 横向-线的一组坐标
        if (beforePlaceCoords) {
          // 管径对比
          const diameterDiff = beforePlace.pipeDiameter - place.pipeDiameter
          // 管底名义坡降
          const nominalSlopePipeBottom = (beforePlace.pipeDownStreamElevation - beforePlace.pipeUpStreamElevation) / beforePlace.wellDistance * 1000

          coords.forEach((item, idx) => {
            if (
              idx !== 2 &&
              idx !== 3 &&
              !isNaN(item[0]) &&
              !isNaN(item[1]) &&
              idx !== coords.length - 1
            ) {
              if (idx === 1) {
                texts.push({
                  x: item[0],
                  y: item[1],
                  text: place.liquidHeight,
                  direction: 'right-top'
                })
              } else if (idx === 7) {
                let text = ''
                if (diameterDiff > 0) {
                  text = '大管接小管'
                }
                if (nominalSlopePipeBottom > 0) {
                  text += text ? '逆坡' : ',逆坡'
                }
                texts.push({
                  x: beforePlaceCoords[idx][0],
                  y: beforePlaceCoords[idx][1],
                  text: beforePlace.wellDistance + 'm',
                  direction: 'right-top'
                })
                texts.push({
                  x: beforePlaceCoords[idx][0],
                  y: beforePlaceCoords[idx][1],
                  text: 'DN' + beforePlace.pipeDiameter,
                  direction: 'right-bottom'
                })
                texts.push({
                  x: item[0],
                  y: item[1],
                  text: nominalSlopePipeBottom.toFixed(2),
                  direction: 'left-top'
                })
                texts.push({
                  x: item[0],
                  y: item[1],
                  text: text,
                  direction: 'left-bottom'
                })
              } else if (idx === 8) {

              }



              // 默认颜色
              let color = 'black'
              if (idx === 1) {
                // 液位颜色
                color = 'blue'
              } else if (idx === 4 || idx === 5) {
                idx -= 2
                if (diameterDiff > 0 || nominalSlopePipeBottom > 0) {
                  // 管道
                  color = 'red'
                }
              } else if (idx === 6) {
                // 黄海基准面,海拔0米
                color = 'green'
              }

              lines.push({
                startX: beforePlaceCoords[idx][0],
                startY: beforePlaceCoords[idx][1],
                endX: item[0],
                endY: item[1],
                color
              })
            }
          })
        }

        beforePlaceCoords = coords

        beforePlace = place
      })

      drawLine(context, lines)
      texts.forEach(item => {
        item.font = '10px sans-serif'
        setTextDirection(context, item)
      })
    }

    /**
     * 初始化canvas 信息
     */
    function initCanvas([canvasDomId, canvasWidth, canvasHeight]) {
      const canvasParent = document.getElementById(canvasDomId)
      if (canvas) {
        canvas.parentNode.removeChild(canvas)
      }
      canvas = document.createElement("canvas")
      canvasParent.appendChild(canvas)
      width = canvasWidth
      height = canvasHeight
      canvas.width = width
      canvas.height = height
      context = canvas.getContext("2d")
    }

    /**
     * 绘制线段
    */
    function drawLine(ctx, data) {
      if (!ctx) return
      if (!Array.isArray(data)) return
      data.forEach((item, index) => {
        ctx.beginPath()
        ctx.moveTo(item.startX, item.startY)
        ctx.lineTo(item.endX, item.endY)
        ctx.strokeStyle = item.color || 'black'
        ctx.stroke()
      })
    }

    /**
     * 通过指定坐标显示文本方位
    */
    function setTextDirByPos(ctx, params) {
      params = Object.assign({
        text: '',
        x: null,
        y: null,
        margin: 5,
        font: '10px sans-serif',
        color: 'black',
        style: 'fill', // 'fill' 'stroke', 填充 边框
        direction: 'left-top' // 'left-top' 'left-center' 'left-bottom' 'center-top' 'center-center' 'center-bottom' 'right-top' 'right-center' 'right-bottom', 左上 左中 左下 中上 中中 中下 右上 右中 右下
      }, params);

      if (!ctx || params.text === '' || !params.x || !params.y) return

      ctx.font = params.font
      const size = ctx.measureText(params.text)
      const height = size.actualBoundingBoxAscent - size.actualBoundingBoxDescent
      switch (params.direction) {
        case 'left-top':
          params.x = params.x - size.width - params.margin
          params.y = params.y - params.margin
          break
        case 'left-center':
          params.x = params.x - size.width - params.margin
          params.y = params.y + height / 2
          break
        case 'left-bottom':
          params.x = params.x - size.width - params.margin
          params.y = params.y + height + params.margin
          break
        case 'center-top':
          params.x = params.x - size.width / 2
          params.y = params.y - height
          break
        case 'center-center':
          params.x = params.x - size.width / 2
          params.y = params.y + height / 2
          break
        case 'center-bottom':
          params.x = params.x - size.width / 2
          params.y = params.y + height
          break
        case 'right-top':
          params.x = params.x + params.margin
          params.y = params.y - params.margin
          break
        case 'right-center':
          params.x = params.x + params.margin
          params.y = params.y + height / 2
          break
        case 'right-bottom':
          params.x = params.x + params.margin
          params.y = params.y + height + params.margin
          break
      }
      ctx.beginPath()
      ctx.fillStyle = params.color
      if (params.style === 'fill') {
        ctx.fillText(params.text, params.x, params.y)
      } else {
        ctx.strokeText(params.text, params.x, params.y)
      }
    }

    /**
     * 设置文本显示位置
    */
    function setTextDirection(ctx, params) {
      params = Object.assign({
        text: '',
        x: null,
        y: null,
        margin: 5,
        font: '10px sans-serif',
        color: 'black',
        style: 'fill', // 'fill' 'stroke', 填充 边框
        direction: 'left-top' // 'left-top' 'left-center' 'left-bottom' 'center-top' 'center-center' 'center-bottom' 'right-top' 'right-center' 'right-bottom', 左上 左中 左下 中上 中中 中下 右上 右中 右下
      }, params);

      if (!ctx || params.text === '' || !params.x || !params.y) return

      ctx.font = params.font
      const size = ctx.measureText(params.text)
      const height = size.actualBoundingBoxAscent - size.actualBoundingBoxDescent
      switch (params.direction) {
        case 'left-top':
          params.x = params.x - size.width - params.margin
          params.y = params.y - params.margin
          break
        case 'left-center':
          params.x = params.x - size.width - params.margin
          params.y = params.y + height / 2
          break
        case 'left-bottom':
          params.x = params.x - size.width - params.margin
          params.y = params.y + height + params.margin
          break
        case 'center-top':
          params.x = params.x - size.width / 2
          params.y = params.y - height
          break
        case 'center-center':
          params.x = params.x - size.width / 2
          params.y = params.y + height / 2
          break
        case 'center-bottom':
          params.x = params.x - size.width / 2
          params.y = params.y + height
          break
        case 'right-top':
          params.x = params.x + params.margin
          params.y = params.y - params.margin
          break
        case 'right-center':
          params.x = params.x + params.margin
          params.y = params.y + height / 2
          break
        case 'right-bottom':
          params.x = params.x + params.margin
          params.y = params.y + height + params.margin
          break
      }
      ctx.beginPath()
      ctx.fillStyle = params.color
      if (params.style === 'fill') {
        ctx.fillText(params.text, params.x, params.y)
      } else {
        ctx.strokeText(params.text, params.x, params.y)
      }
    }
  </script>
</body>

</html>